﻿using MiniFox.Net.Models;
using MiniFox.Resources;
using MiniFox.Utility;
using MiniFox.Web;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Net.Mime;
using System.Threading.Tasks;

namespace MiniFox.Net.Http
{
    /// <summary>
    /// 
    /// </summary>
    public class HttpService : Service
    {
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<BeforeHttpSendEventArgs> BeforeSend;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<AfterHttpSendEventArgs> AfterSend;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="setting"></param>
        /// <param name="adapter"></param>
        public HttpService(IServiceSetting setting, HttpAdapter adapter) : base(setting, adapter)
        {
        }
        /// <summary>
        /// 
        /// </summary>
        public override Uri FullURI
        {
            get
            {
                string url = WebUtil.BuildFullUrl(this.WebAdapter.FullHost, this.Url);
                return new Uri(url);
            }
        }

        private HttpRequestMessage CreateHttpRequest(HttpMethod httpMethod, object query, object headers)
        {
            HttpRequestMessage request = new HttpRequestMessage(httpMethod, this.FullURI);
            request.AddUriParameters(this.Parameters);
            SetRequestMessage(request, query, headers);
            return request;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        protected virtual void SetRequestMessage(HttpRequestMessage request, object query, object headers)
        {
            request.AddUriParameters(query);
            request.AddHttpHeaders(headers);
        }

        private HttpClient CreateHttpClient()
        {
            var handler = CreateHttpMessageHandler();
            var httpClient = new HttpClient(handler, true);
            httpClient.Timeout = this.Timeout;
            SetDefaultHeaders(httpClient.DefaultRequestHeaders);
            return httpClient;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected virtual HttpMessageHandler CreateHttpMessageHandler()
        {
            var handler = new HttpClientHandler();
            return handler;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="headers"></param>
        protected virtual void SetDefaultHeaders(HttpRequestHeaders headers)
        {
            foreach (var pair in this.Headers)
            {
                headers.Add(pair.Key, pair.Value);
            }
        }

        //#region http sync
        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="request"></param>
        ///// <returns></returns>
        //public HttpResponseMessage Send(HttpRequestMessage request)
        //{
        //    using (var httpClient = CreateHttpClient())
        //    {
        //        this.BeforeSend?.Invoke(this, new BeforeHttpSendEventArgs(request));
        //        var response = httpClient.Send(request);
        //        this.AfterSend?.Invoke(this, new AfterHttpSendEventArgs(response));
        //        return response;
        //    }
        //}
        //#endregion

        #region http async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
        {
            using (HttpClient httpClient = CreateHttpClient())
            {
                if (request.Method == HttpMethod.Post || request.Method == HttpMethod.Put)
                    request.Content.Headers.ContentType = new MediaTypeHeaderValue(this.Setting.ContentType);
                this.BeforeSend?.Invoke(this, new BeforeHttpSendEventArgs(request));
                var response = await httpClient.SendAsync(request);
                this.AfterSend?.Invoke(this, new AfterHttpSendEventArgs(response));
                return response;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <param name="httpContent"></param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> SendAsync(HttpMethod httpMethod, object query, object headers, HttpContent httpContent = null)
        {
            using (HttpClient httpClient = CreateHttpClient())
            {
                var request = this.CreateHttpRequest(httpMethod, query, headers);
                request.Content = httpContent;
                this.BeforeSend?.Invoke(this, new BeforeHttpSendEventArgs(request));
                var response = await httpClient.SendAsync(request);
                this.AfterSend?.Invoke(this, new AfterHttpSendEventArgs(response));
                return response;
            }
        }
        #endregion

        #region GET 

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Task<HttpContent> GetAsync()
        {
            return this.GetAsync(null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<HttpContent> GetAsync(object query)
        {
            return this.GetAsync(query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<HttpContent> GetAsync(object query, object headers)
        {
            var request = this.CreateHttpRequest(HttpMethod.Get, query, headers);
            var response = await this.SendAsync(request);
            return response.Content;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <returns></returns>
        public Task<RS> GetAsync<RS>()
        {
            return this.GetAsync<RS>(null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<RS> GetAsync<RS>(object query)
        {
            return this.GetAsync<RS>(query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<RS> GetAsync<RS>(object query, object headers)
        {
            var content = await this.GetAsync(query, headers);
            return await content.ReadFromJsonAsync<RS>();
        }
        #endregion

        #endregion

        #region POST 

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public Task<HttpContent> PostAsync(HttpContent content)
        {
            return this.PostAsync(content, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<HttpContent> PostAsync(HttpContent content, object query)
        {
            return this.PostAsync(content, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<HttpContent> PostAsync(HttpContent content, object query, object headers)
        {
            var request = CreateHttpRequest(HttpMethod.Post, query, headers);
            request.Content = content;
            var response = await this.SendAsync(request);
            return response.Content;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        public Task<RS> PostAsync<RQ, RS>(RQ data)
        {
            return this.PostAsync<RQ, RS>(data, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<RS> PostAsync<RQ, RS>(RQ data, object query)
        {
            return this.PostAsync<RQ, RS>(data, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<RS> PostAsync<RQ, RS>(RQ data, object query, object headers)
        {
            var requestContent = new StringContent(data.ToJsonString());
            var content = await this.PostAsync(requestContent, query, headers);
            return await content.ReadFromJsonAsync<RS>();
        }

        #endregion

        #endregion

        #region PUT 

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public Task<HttpContent> PutAsync(HttpContent content)
        {
            return this.PutAsync(content, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<HttpContent> PutAsync(HttpContent content, object query)
        {
            return this.PutAsync(content, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<HttpContent> PutAsync(HttpContent content, object query, object headers)
        {
            var request = CreateHttpRequest(HttpMethod.Put, query, headers);
            request.Content = content;
            var response = await this.SendAsync(request);
            return response.Content;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        public Task<RS> PutAsync<RQ, RS>(RQ data)
        {
            return this.PutAsync<RQ, RS>(data, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<RS> PutAsync<RQ, RS>(RQ data, object query)
        {
            return this.PutAsync<RQ, RS>(data, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<RS> PutAsync<RQ, RS>(RQ data, object query, object headers)
        {
            var requestContent = new StringContent(data.ToJsonString());
            var content = await this.PutAsync(requestContent, query, headers);
            return await content.ReadFromJsonAsync<RS>();
        }

        #endregion

        #endregion

        #region DELETE 

        #region async

        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public Task<HttpContent> DeleteAsync(HttpContent content)
        {
            return this.DeleteAsync(content, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<HttpContent> DeleteAsync(HttpContent content, object query)
        {
            return this.DeleteAsync(content, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<HttpContent> DeleteAsync(HttpContent content, object query, object headers)
        {
            var request = CreateHttpRequest(HttpMethod.Delete, query, headers);
            request.Content = content;
            var response = await this.SendAsync(request);
            return response.Content;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        public Task<RS> DeleteAsync<RQ, RS>(RQ data)
        {
            return this.DeleteAsync<RQ, RS>(data, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<RS> DeleteAsync<RQ, RS>(RQ data, object query)
        {
            return this.DeleteAsync<RQ, RS>(data, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="data"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<RS> DeleteAsync<RQ, RS>(RQ data, object query, object headers)
        {
            var requestContent = new StringContent(data.ToJsonString());
            var content = await this.DeleteAsync(requestContent, query, headers);
            return await content.ReadFromJsonAsync<RS>();
        }

        #endregion

        #endregion

        #region HEAD 

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Task<HttpResponseMessage> HeadAsync()
        {
            return this.HeadAsync(null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<HttpResponseMessage> HeadAsync(object query)
        {
            return this.HeadAsync(query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public Task<HttpResponseMessage> HeadAsync(object query, object headers)
        {
            var request = CreateHttpRequest(HttpMethod.Head, query, headers);
            return this.SendAsync(request);

        }
        #endregion

        #endregion


        #region send multipart

        #region async

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileDatas"></param>
        /// <returns></returns>
        public Task<HttpContent> SendAsync(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas)
        {
            return SendAsync(fileDatas, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileDatas"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<HttpContent> SendAsync(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object query)
        {
            return SendAsync(fileDatas, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileDatas"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public virtual async Task<HttpContent> SendAsync(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object query, object headers)
        {
            MultipartContent content = CreateMultipartContent(fileDatas);
            return await this.PostAsync(content, query, headers);
        }

        private MultipartContent CreateMultipartContent(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas)
        {
            MultipartContent content = new("form-data", $"----{SequenceGUID.NewGUID()}");
            foreach (var fileData in fileDatas)
            {
                //$"{begin}Content-Disposition: form-data; name=\"{formName}\"; filename=\"{fileName}\"\r\nContent-Type: {mimeType}{end}"
                ByteArrayContent subContent = new(fileData.data);
                subContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
                subContent.Headers.Add("name", fileData.formName);
                subContent.Headers.Add("fileName", fileData.fileName);
                subContent.Headers.ContentType = new MediaTypeHeaderValue(fileData.mimeType);
                content.Add(subContent);
            }
            return content;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="fileDatas"></param>
        /// <returns></returns>
        public Task<RS> SendAsync<RS>(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas)
        {
            return SendAsync<RS>(fileDatas, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="fileDatas"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task<RS> SendAsync<RS>(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object query)
        {
            return SendAsync<RS>(fileDatas, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="fileDatas"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<RS> SendAsync<RS>(IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object query, object headers)
        {
            var content = await this.SendAsync(fileDatas, query, headers);
            return await content.ReadFromJsonAsync<RS>();
        }
        #endregion

        #endregion

    }


    /// <summary>
    /// 
    /// </summary>
    public class BeforeHttpSendEventArgs : EventArgs
    {
        string _method;
        string _url;
        object _headers;
        HttpContent _content;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        public BeforeHttpSendEventArgs(HttpRequestMessage request)
        {
            _url = request.RequestUri.ToString();
            _method = request.Method.Method;
            _headers = request.Headers;
            _content = request.Content;
        }
        /// <summary>
        /// 
        /// </summary>
        public HttpContent RequestContent
        {
            get { return _content; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string Url
        {
            get { return _url; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string HttpMethod
        {
            get { return _method; }
        }
        /// <summary>
        /// 
        /// </summary>
        public object Headers
        {
            get { return _headers; }
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public class AfterHttpSendEventArgs : EventArgs
    {
        HttpContent _content;
        MediaTypeHeaderValue _contentType;
        object _headers;
        HttpStatusCode _statusCode;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="response"></param>
        public AfterHttpSendEventArgs(HttpResponseMessage response)
        {
            _contentType = response.Content.Headers.ContentType;
            _content = response.Content;
            _headers = response.Headers;
            _statusCode = response.StatusCode;
        }
        /// <summary>
        /// 
        /// </summary>
        public HttpContent ResponseContent
        {
            get { return _content; }
        }
        /// <summary>
        /// 
        /// </summary>
        public MediaTypeHeaderValue ContentType
        {
            get { return _contentType; }
        }
        /// <summary>
        /// 
        /// </summary>
        public HttpStatusCode StatusCode
        {
            get { return _statusCode; }
        }
        /// <summary>
        /// 
        /// </summary>
        public object Headers
        {
            get { return _headers; }
        }
    }
}
